package com.anb.common;

import org.w3c.dom.Document;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.io.File;
import java.lang.reflect.Constructor;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class Utils {
    public static final Map<String, Long> BINARY_UNITS = new HashMap<String, Long>();
    public static final Map<String, Long> DURATION_UNITS = new HashMap<String, Long>();
    static {
        BINARY_UNITS.put("B", 1L);
        BINARY_UNITS.put("K", 1L * 1024);
        BINARY_UNITS.put("M", 1L * 1024 * 1024);
        BINARY_UNITS.put("G", 1L * 1024 * 1024 * 1024);
        //BINARY_UNITS.put("T", 1L * 1024 * 1024 * 1024 * 1024);

        DURATION_UNITS.put("MS", 1L);
        DURATION_UNITS.put("S" , 1L * 1000);
        DURATION_UNITS.put("M" , 1L * 1000 * 60);
        DURATION_UNITS.put("H" , 1L * 1000 * 60 * 60);
        DURATION_UNITS.put("D" , 1L * 1000 * 60 * 60 *24);
        DURATION_UNITS.put("W", 1L * 1000 * 60 * 60 * 24 * 7);
    }
    public static long parseBinary(String str) {
        return parseQuantity(str, BINARY_UNITS);
    }
    public static long parseDuration(String str) {
        return parseQuantity(str, DURATION_UNITS);
    }
    public static long parseQuantity(String str, Map<String, Long> units) {
        Pattern pattern = Pattern.compile("^(?<digit>[0-9]+)(?<unit>[a-zA-Z]*)");
        long value = 0;
        int len = str.length();
        int pos = 0;
        while (pos < len) {
            Matcher m = pattern.matcher(str.subSequence(pos, len));
            if (!m.find()) {
                throw new IllegalArgumentException("invalid format '" + str.substring(pos) + "'");
            }
            pos += m.end();
            long digit = Long.parseLong(m.group(1));
            String u = m.group(2);
            if (u != null && !u.isEmpty()) {
                if (!units.containsKey(u)) {
                    throw new IllegalArgumentException("invalid unit '" + u + "'");
                }
                long unit = units.get(u);
                digit *= unit;
            }
            value += digit;
        }
        return value;
    }

    public static Document loadDocument(File file) throws Exception {
        DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
        DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
        Document document = documentBuilder.parse(file);
        document.getDocumentElement().normalize();
        return document;
    }

    public static String joinString(String delimiter, Iterable<?> collection) {
        boolean first = true;
        StringBuffer buffer = new StringBuffer();
        for (Object o : collection) {
            if (!first) {
                buffer.append(delimiter);
            }
            buffer.append(o.toString());
            first = false;
        }
        return buffer.toString();
    }

    public static Class<?> getClass(String className, String[] namespaces) throws ClassNotFoundException {
        if (className == null || className.isEmpty()) {
            return  Void.class;
        }
        if (className.equals("int8")) {
            return char.class;
        }
        if (className.equals("int16")) {
            return short.class;
        }
        if (className.equals("int32")) {
            return int.class;
        }
        if (className.equals("int64")) {
            return long.class;
        }
        if (className.equals("string")) {
            return String.class;
        }
        if (className.equals("bool")) {
            return boolean.class;
        }
        if (className.equals("bytes")) {
            return byte[].class;
        }

        List<String> trials = new ArrayList<String>();
        trials.add(className);
        Class<?> clazz = _getClass(className);
        if (clazz != null) {
            return clazz;
        }
        if (namespaces != null) {
            for (String namespace : namespaces) {
                String className1 = namespace + "." + className;
                trials.add(className1);
                clazz = _getClass(className1);
                if (clazz != null) {
                    return clazz;
                }

                String className2 = namespace + "$" + className;
                trials.add(className2);
                clazz = _getClass(className2);
                if (clazz != null) {
                    return clazz;
                }
            }
        }
        throw new ClassNotFoundException("class not found for " + className + ", tried (" + joinString(", ", trials) + ")");
    }
    private static Class<?> _getClass(String className) {
        try {
            return Class.forName(className);
        } catch (ClassNotFoundException e) {
            return null;
        }
    }

    public static Constructor<?> matchConstructor(Class<?> clazz, Object[] args) throws Exception {
        int len = args.length;
        Class<?>[] argTypes = new Class<?>[len];
        for (int i = 0; i < len; i++) {
            argTypes[i] = args[i].getClass();
        }
        return matchConstructor(clazz, argTypes);
    }

    public static Constructor<?> matchConstructor(Class<?> clazz, Class<?>[] argTypes) throws Exception {
        int len = argTypes.length;
        for (Constructor<?> constructor : clazz.getConstructors()) {
            Class<?>[] parameterTypes = constructor.getParameterTypes();
            if (parameterTypes.length != len) {
                continue;
            }

            int i;
            for (i = 0; i < len; i++) {
                if (!parameterTypes[i].isAssignableFrom(argTypes[i])) {
                    break;
                }
            }
            if (i != len) {
                continue;
            }
            return constructor;
        }
        throw new Exception("no suitable constructor");
    }

    public static Object newInstance(Class<?> clazz, Object[] args) throws Exception {
        Constructor<?> constructor = matchConstructor(clazz, args);
        return constructor.newInstance(args);
    }

    public static String decapitalize(String str) {
        if (str.isEmpty()) {
            return str;
        }
        return str.substring(0, 1).toLowerCase() + str.substring(1);
    }

    public static Object[] flat(Object... args) {
        List<Object> list = new ArrayList<Object>();
        for (Object arg : args) {
            if (arg instanceof Iterable<?>) {
                Iterable<?> iterable = (Iterable < ?>)arg;
                Iterator<?> iterator = iterable.iterator();
                while (iterator.hasNext()) {
                    list.add(iterator.next());
                }
                continue;
            }
            if (arg instanceof Object[]) {
                Object[] objs = (Object[])arg;
                for (Object obj : objs) {
                    list.add(obj);
                }
                continue;
            }
            list.add(arg);
        }
        return list.toArray();
    }
}

